import type { LoginResult, TokenResult } from "@/api/types";
import Cookies from "js-cookie";
import { deptApi } from "@/api";
import { tokenKey, tokenStorage, userDeptStorage, userStorage } from "@/config/storage";
import { useUserStore } from "@/stores/user";
import { useUserDeptStore } from "@/stores/userDept";

/**
 * 通过`multiple-tabs`是否在`cookie`中，判断用户是否已经登录系统，
 * 从而支持多标签页打开已经登录的系统后无需再登录。
 * 没有免登录的情况下, 浏览器完全关闭后`multiple-tabs`将自动从`cookie`中销毁，
 * 再次打开浏览器需要重新登录系统(通过全局路由守卫校验)
 */
export const multipleTabsKey = "multiple-tabs";

/**
 * 获取存储在`cookie`或`localStorage`中的`token`
 */
export function getToken(): TokenResult | null {
    const token = Cookies.get(tokenKey);
    return token ? JSON.parse(token) : tokenStorage.value;
}

/**
 * 设置`token`以及一些必要信息并采用无感刷新`token`方案
 * 无感刷新：后端返回`accessToken`（访问接口使用的`token`）、`refreshToken`（用于调用刷新`accessToken`的接口时所需的`token`，`refreshToken`的过期时间（比如30天）应大于`accessToken`的过期时间（比如2小时））、`expires`（`accessToken`的过期时间）
 * 将`accessToken`、`expires`、`refreshToken`这三条信息放在key值为authorized-token的cookie里（过期自动销毁）
 * 将`avatar`、`username`、`nickname`、`roles`、`permissions`、`refreshToken`、`expires`这七条信息放在key值为`user-info`的localStorage里（利用`multipleTabsKey`当浏览器完全关闭后自动销毁）
 */
export function setToken(data: TokenResult): void {
    const { accessToken, refreshToken, expires } = data;
    const { isRemembered, loginDay } = useUserStore();

    const cookieString = JSON.stringify({ accessToken, refreshToken, expires });

    // expires是后端返回的一个过期时间戳，单位是毫秒
    expires > 0
        // 设置cookie的过期时间为expires减去当前时间的毫秒数转换为天数
        ? Cookies.set(tokenKey, cookieString, { expires: (expires - Date.now()) / (1000 * 60 * 60 * 24) })
        // Note[js-cooke] set 方法形参 options 不传 expires 参数时, cookie 截止至用户退出浏览器失效
        : Cookies.set(tokenKey, cookieString);

    setStorageToken(data);

    // 用于判断是否免登录, 如果用户勾选了记住登录，则将 multiple-tabs 设置为持久化 cookie
    // 否则设置为会话cookie（浏览器关闭后失效）
    if (!Cookies.get(multipleTabsKey)) {
        Cookies.set(multipleTabsKey, "true", isRemembered ? { expires: loginDay } : {});
    }
}

/**
 * 删除`token`以及相关的 localStorage
 */
export function removeToken(reload: boolean = false, timeout: number = 0): void {
    Cookies.remove(tokenKey);
    Cookies.remove(multipleTabsKey);
    userStorage.value = null; // 等同于localStorage.removeItem(userKey);
    tokenStorage.value = null;
    if (reload) {
        setTimeout(() => {
            location.reload(); // 刷新页面，触发路由守卫跳转到登录页
        }, timeout);
    }
}

/**
 * 用于请求头部携带`token`，Bearer是一种方案: https://www.jianshu.com/p/61d592ae33ee
 * 这里后端做了校验, 如果不携带 Bearer 过不了验证
 */
export const formatToken = (token: string): string => `Bearer ${token}`;

/**
 * 登录后存储用户信息到 pinia 和 localStorage 中
 */
export function setLoginInfo(data: LoginResult): void {
    const { avatar, username, nickname, roles, deptId, createdBy, createdAt, updatedBy, updatedAt } = data;

    // Note[pinia]: 推荐每次都使用 useStore() 来获取 Store 实例, 而不是设置 Store 对象, pinia做了很多优化
    useUserStore().SET_AVATAR(avatar);
    useUserStore().SET_USERNAME(username);
    useUserStore().SET_NICKNAME(nickname);
    useUserStore().SET_ROLES(roles);
    useUserStore().SET_DEPT_ID(deptId);
    useUserStore().SET_CREATED_BY(createdBy);
    useUserStore().SET_CREATED_AT(createdAt);
    useUserStore().SET_UPDATED_BY(updatedBy);
    useUserStore().SET_UPDATED_AT(updatedAt);

    // 更新 localStorage 中的用户信息
    userStorage.value = Object.assign(userStorage.value ?? {}, {
        avatar,
        username,
        nickname,
        roles,
        deptId,
        createdBy,
        createdAt,
        updatedBy,
        updatedAt,
    });

    setStorageToken(data);
}

/**
 * 更新 localStorage 中的 token 信息
 */
export function setStorageToken(data: TokenResult): void {
    const { accessToken, refreshToken, expires } = data;
    // 更新 localStorage 中的 token 信息
    tokenStorage.value = Object.assign(tokenStorage.value ?? {}, {
        accessToken,
        refreshToken,
        expires,
    });
}

/**
 * 存储用户部门信息到 localStorage 中
 */
export function setUserDept(departId: string): void {
    deptApi.findById(departId).then((resp) => {
        if (ok(resp)) {
            useUserDeptStore().SET_DEPT(resp.data);
            userDeptStorage.value = resp.data;
        }
    });
}
